package net.ankao.org.easylock.aop;

import net.ankao.org.easylock.annotation.EasyLock;
import net.ankao.org.easylock.core.EasyLockCallback;
import net.ankao.org.easylock.core.EasyLockTemplate;
import net.ankao.org.easylock.utils.SpElUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;

import java.lang.reflect.Method;

/**
 * <p>
 * EasyLockAspect
 * </p>
 *
 * @author ankao.net
 * @since 2022/3/28
 */
@Aspect
@Component
public class EasyLockAspect {

    /**
     * 前缀
     */
    public static final String LOCK_PREFIX = "easy-lock:";

    @Autowired
    private EasyLockTemplate lockTemplate;

    @Pointcut("@annotation(easyLock) ")
    public void distributedLockPointcut(EasyLock easyLock) {
    }

    @Around(value = "distributedLockPointcut(easyLock)")
    public Object distributedLockAround(ProceedingJoinPoint joinPoint, EasyLock easyLock) throws Throwable {
        Method method = this.getSpecificMethod(joinPoint);
        return (Boolean) SpElUtil.parse(joinPoint.getTarget(), easyLock.condition(), method, joinPoint.getArgs()) ?
                lock(joinPoint, easyLock, SpElUtil.parse(joinPoint.getTarget(), easyLock.value(), method, joinPoint.getArgs()).toString()) : joinPoint.proceed();
    }

    @AfterThrowing(value = "distributedLockPointcut(easyLock)", throwing = "ex")
    public void afterThrowing(Throwable ex, EasyLock easyLock) throws Throwable {
        throw ex;
    }

    private Method getSpecificMethod(ProceedingJoinPoint pjp) {
        // 获得真实代理类
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        Method method = methodSignature.getMethod();
        Class<?> targetClass = AopProxyUtils.ultimateTargetClass(pjp.getTarget());
        if (targetClass == null && pjp.getTarget() != null) {
            targetClass = pjp.getTarget().getClass();
        }
        Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
        specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
        return specificMethod;
    }

    private Object lock(ProceedingJoinPoint pjp, EasyLock ann, final String lockName) throws Throwable {
        String rLockName = LOCK_PREFIX + lockName;
        if (ann.tryLock()) {
            return lockTemplate.tryLock(new EasyLockCallback<Object>() {
                @Override
                public Object process() throws Throwable {
                    return pjp.proceed();
                }

                @Override
                public String getLockName() {
                    return rLockName;
                }

                @Override
                public String getExceptionDesc() {
                    return ann.exceptionDesc();
                }
            }, ann.waitTime(), ann.leaseTime(), ann.timeUnit(), ann.fairLock());
        } else {
            return lockTemplate.lock(new EasyLockCallback<Object>() {
                @Override
                public Object process() throws Throwable {
                    return pjp.proceed();
                }

                @Override
                public String getLockName() {
                    return rLockName;
                }

                @Override
                public String getExceptionDesc() {
                    return ann.exceptionDesc();
                }
            }, ann.fairLock());
        }
    }
}
